
from prettytable import PrettyTable


class Interface(object):
    def __init__(self, index, router_id):
        self.index = index
        self.ip_address = None
        self.childs = []


class SimuRouter(object):
    
    total_simu_router_num = 0

    def __init__(self, parent, interface, child_index, topology):
        self.id = self.total_simu_router_num
        SimuRouter.total_simu_router_num += 1
        self.index_as_child = child_index
        self.system_id = None
        self.router_id = None
        self.interface_to_parent = Interface(0, self.id)
        self.interfaces = []
        #self.interfaces.append(Interface(len(self.interfaces), self.id))
        self.parent = parent
        self.belonged_interface = interface
        self.total_child_num = 0
        self.belonged_topology = topology
        self.max_interface_num_towards_child = (topology.max_interface_num - 1) if parent else topology.max_interface_num
        self.max_child_per_interface = self.belonged_topology.max_router_per_transit_network - 1
        

    def index_in_interface(self):
        return self.index_as_child % self.max_child_per_interface

    def find_brother(self):
        if not self.parent:
            return None
        else:
            if self.index_in_interface() < (self.max_child_per_interface - 1):
                return self.belonged_interface.childs[self.index_in_interface() + 1]
            else:
                if self.belonged_interface.index < (self.parent.max_interface_num_towards_child - 1):
                    return self.parent.interfaces[self.belonged_interface.index + 1].childs[0]
                else:
                    return None

    def find_child(self, index):
        if not self.has_child():
            return None
        if index < self.total_child_num:
            return self.interfaces[index // self.max_child_per_interface].childs[index % self.max_child_per_interface]
        else:
            return None

    def has_child(self):
        return bool(self.total_child_num)

    def find_first_descendant(self):
        if not self.parent:
            return self.interfaces[0].childs[0]
        else:
            return self.parent.interfaces[0].childs[0].interfaces[0].childs[0]

    def add_child(self):
        if self.total_child_num >= self.max_interface_num_towards_child * self.max_child_per_interface:
            return False

        if len(self.interfaces):
            intf = self.interfaces[-1] 
        else:
            intf = Interface(len(self.interfaces)+1, self.id)
            self.interfaces.append(intf)
            
        if len(intf.childs) < self.max_child_per_interface:
            child = SimuRouter(self, intf, self.total_child_num, self.belonged_topology)
            intf.childs.append(child)
            self.total_child_num += 1
            return True
        elif len(self.interfaces) < self.max_interface_num_towards_child:
            intf = Interface(len(self.interfaces)+1, self.id)
            self.interfaces.append(intf)
            return self.add_child()
        else:
            return False

class TopologyGenerator(object):
    def __init__(self, max_simu_num, max_intf_num, max_router_per_network):
        self.__max_simu_router_num = max_simu_num
        self.__cur_simu_router_num = 0
        self.max_interface_num = max_intf_num
        self.max_router_per_transit_network = max_router_per_network

    def generate_topology(self, emu_router):
        cur_router = emu_router
        while self.__cur_simu_router_num < self.__max_simu_router_num:
            if cur_router.add_child():
                self.__cur_simu_router_num += 1
            else:
                brother = cur_router.find_brother()
                if brother:
                    if brother.add_child():
                        self.__cur_simu_router_num += 1
                        cur_router = brother
                    else:
                        raise Exception('Wrong Topylogy Data for brother level!')
                else:
                    first_descendant = cur_router.find_first_descendant()
                    if first_descendant.add_child():
                        self.__cur_simu_router_num += 1
                        cur_router = first_descendant
                    else:
                        raise Exception('Wrong Topylogy Data for descendant level!')

class ConfigAllocation(object):
    def __init__(self):
        self.cur_router = None
        self.cur_child_index = 0
        self.start_system_id = 1
        self.start_ip_addr = 100
        self.start_ip_num = 0

    def alloc_system_id(self, router):
        router.system_id = self.start_system_id
        self.start_system_id += 1

    def alloc_parent_interface_ip(self, router):
        router.interface_to_parent.ip_address = self.start_ip_addr
        self.start_ip_addr += 100

    def alloc_child_interface_ip(self, router):
        for intf in router.interfaces:
            if not intf.ip_address:
                intf.ip_address = self.start_ip_addr
                self.start_ip_addr += 1

    def alloc_interface_ip(self, router):
        if router.belonged_interface:
            router.interface_to_parent.ip_address = router.belonged_interface.ip_address + router.index_in_interface() + 1
        for intf in router.interfaces:
            if not intf.ip_address:
                intf.ip_address = self.start_ip_addr + self.start_ip_num
                self.start_ip_num += 100

    def __reset_cur_router(self, new_router):
        self.cur_router = new_router
        self.cur_child_index = 0

    def alloc_configrations(self, emu_router):
        self.cur_router = emu_router
        emu_router.system_id = 0
        self.alloc_interface_ip(emu_router)

        while True:
            child = self.cur_router.find_child(self.cur_child_index)
            if child:
                self.alloc_system_id(child)
                self.alloc_interface_ip(child)
                self.cur_child_index += 1
            else:
                brother = self.cur_router.find_brother()
                if brother:
                    self.alloc_interface_ip(brother)
                    if brother.has_child():
                        self.__reset_cur_router(brother)
                    else:
                        break
                else:
                    descendant = self.cur_router.find_first_descendant()
                    self.alloc_interface_ip(descendant)
                    if descendant.has_child():
                        self.__reset_cur_router(descendant)
                    else:
                        break


def record_router_info(router, table):
    neighbors1 = ''
    for neighbor in router.interface_to_parent.childs:
        neighbors1 += 'router-'+str(neighbor.id)+' '
    
    table.add_row(['router-'+str(router.id)+' '+str(router.system_id), 
                    'intf-'+str(router.interface_to_parent.index), 
                    'IP-'+str(router.interface_to_parent.ip_address), 
                    neighbors1])

    for intf in router.interfaces:
        neighbors = ''
        for neighbor in intf.childs:
            neighbors += 'router-'+str(neighbor.id)+' '
        
        table.add_row(['router-'+str(router.id)+' '+str(router.system_id), 
                       'intf-'+str(intf.index), 
                       'IP-'+str(intf.ip_address), 
                       neighbors])

def show_topology(emu_router):
    table = PrettyTable(['Router Id', 'Interface Id', 'Ip Address', 'Child Id'])
    record_router_info(emu_router, table)

    cur_router = emu_router
    cur_child_index = 0
    while True:
        child = cur_router.find_child(cur_child_index)
        if child:
            record_router_info(child, table)
            cur_child_index += 1
        else:
            brother = cur_router.find_brother()
            if brother:
                if brother.has_child():
                    cur_router = brother
                    cur_child_index = 0
                else:
                    break
            else:
                descendant = cur_router.find_first_descendant()
                if descendant.has_child():
                    cur_router = descendant
                    cur_child_index = 0
                else:
                    break
    
    print(table)
    

if __name__ == '__main__':
    generator = TopologyGenerator(22, 3, 4)
    emu_router = SimuRouter(None, None, 0, generator)
    generator.generate_topology(emu_router)
    config_alloc = ConfigAllocation()
    config_alloc.alloc_configrations(emu_router)
    show_topology(emu_router)
